// ---------------------------------------------------------------------------
//
// ESGI OpenGL (ES) 2.0 Framework
// Malek Bengougam, 2012							malek.bengougam@gmail.com
//
// ---------------------------------------------------------------------------

#ifndef DEF_Mat
#define DEF_Mat

// --- Includes --------------------------------------------------------------

#include "Vector.h"
#include <xmmintrin.h>
#include <emmintrin.h>

// --- Classes ---------------------------------------------------------------

// --- Mat2

template <typename T>
struct Mat2 
{
	inline Mat2() {}
    inline Mat2(const T* m)
    {
        X.x = m[0]; X.y = m[1];
        Y.x = m[2]; Y.y = m[3];
    }

	inline void Identity()
    {
        X.x = 1; X.y = 0;
        Y.x = 0; Y.y = 1;
    }

	inline void Transpose()
	{
		T temp = X.y;
		X.y = Y.x;
		Y.x = temp;
	}

    Vector2 x;
    Vector2 y;
};

// --- Mat3

template <typename T>
struct Mat3 
{
	inline Mat3() {}    
    inline Mat3(const T* m)
    {
        X.x = m[0]; X.y = m[1]; X.z = m[2];
        Y.x = m[3]; Y.y = m[4]; Y.z = m[5];
        Z.x = m[6]; Z.y = m[7]; Z.z = m[8];
    }
    inline Mat3(Vector3 _x, Vector3 _y, Vector3 _z) : X(_x), Y(_y), Z(_z)
    {
    }

	inline void Identity()
	{
        X.x = 1; X.y = 0; X.z = 0;
        Y.x = 0; Y.y = 1; Y.z = 0;
        Z.x = 0; Z.y = 0; Z.z = 1;
    }

	inline void Transpose()
	{
		T temp0 = X.y; X.y = Y.x; Y.x = temp0;
		T temp1 = X.z; X.z = Z.x; Z.x = temp1;
		T temp2 = Y.z; Y.z = Z.y; Z.y = temp2;
	}

    inline Mat3 Transposed() const
    {
        Mat3 m;
        m.X.x = X.x; m.X.y = Y.x; m.X.z = Z.x;
        m.Y.x = X.y; m.Y.y = Y.y; m.Y.z = Z.y;
        m.Z.x = X.z; m.Z.y = Y.z; m.Z.z = Z.z;
        return m;
    }

    Vector3 X;
    Vector3 Y;
    Vector3 Z;
};

// --- Mat4

template <typename T>
struct Mat4 
{
	inline Mat4() {}
    inline Mat4(const Mat3<T>& m)
    {
        X.x = m.X.x; X.y = m.X.y; X.z = m.X.z; X.w = 0;
        Y.x = m.Y.x; Y.y = m.Y.y; Y.z = m.Y.z; Y.w = 0;
        Z.x = m.Z.x; Z.y = m.Z.y; Z.z = m.Z.z; Z.w = 0;
        W.x = 0; W.y = 0; W.z = 0; W.w = 1;
    }
    inline Mat4(const T* m)
    {
        X.x = m[0];  X.y = m[1];  X.z = m[2];  X.w = m[3];
        Y.x = m[4];  Y.y = m[5];  Y.z = m[6];  Y.w = m[7];
        Z.x = m[8];  Z.y = m[9];  Z.z = m[10]; Z.w = m[11];
        W.x = m[12]; W.y = m[13]; W.z = m[14]; W.w = m[15];
    }

	inline void operator=(const Mat4<T>& rhs)
	{
		X.x = rhs.X.x;
		X.y = rhs.X.y;
		X.z = rhs.X.z;
		X.w = rhs.X.w;

		Y.x = rhs.Y.x;
		Y.y = rhs.Y.y;
		Y.z = rhs.Y.z;
		Y.w = rhs.Y.w;

		Z.x = rhs.Z.x;
		Z.y = rhs.Z.y;
		Z.z = rhs.Z.z;
		Z.w = rhs.Z.w;

		W.x = rhs.W.x;
		W.y = rhs.W.y;
		W.z = rhs.W.z;
		W.w = rhs.W.w;
	}

	inline void Identity()
    {
        X.x = 1; X.y = 0; X.z = 0; X.w = 0;
        Y.x = 0; Y.y = 1; Y.z = 0; Y.w = 0;
        Z.x = 0; Z.y = 0; Z.z = 1; Z.w = 0;
        W.x = 0; W.y = 0; W.z = 0; W.w = 1;
    }

	inline void Transpose()
	{
		T temp0 = X.y; X.y = Y.x; Y.x = temp0;
		T temp1 = X.z; X.z = Z.x; Z.x = temp1;
		T temp2 = Y.z; Y.z = Z.y; Z.y = temp2;
		T temp3 = W.x; W.x = X.w; X.w = temp3;
		T temp4 = Y.w; Y.w = W.y; W.y = temp4;
		T temp5 = Z.w; Z.w = W.z; W.z = temp5;
	}

	inline Mat4 Transposed() const
    {
        Mat4 m;
        m.X.x = X.x; m.X.y = Y.x; m.X.z = Z.x; m.X.w = W.x;
        m.Y.x = X.y; m.Y.y = Y.y; m.Y.z = Z.y; m.Y.w = W.y;
        m.Z.x = X.z; m.Z.y = Y.z; m.Z.z = Z.z; m.Z.w = W.z;
        m.W.x = X.w; m.W.y = Y.w; m.W.z = Z.w; m.W.w = W.w;
        return m;
    }

	// multiplie un vecteur colonne par une matrice
	// l'ordre implicite est la post-multiplication
    inline Vect4<T> operator *(const Vect4<T>& rhs) const
    {
        Vect4<T> v;
        v.x = X.x * rhs.x + Y.x * rhs.y + Z.x * rhs.z + W.x * rhs.w;
        v.y = X.y * rhs.x + Y.y * rhs.y + Z.y * rhs.z + W.y * rhs.w;
        v.z = X.z * rhs.x + Y.z * rhs.y + Z.z * rhs.z + W.z * rhs.w;
        v.w = X.w * rhs.x + Y.w * rhs.y + Z.w * rhs.z + W.w * rhs.w;
        return v;
    }

	union
	{
		struct
		{
			Vector4 X;
			Vector4 Y;
			Vector4 Z;
			Vector4 W;
		};
		float m[4][4];
	};
};

/*
struct Mat4sse
{
	inline Mat4sse(){}
	inline ~Mat4sse(){}

	inline void Identity()
	{
		X = _mm_setr_ps(1.f, 0.f, 0.f, 0.f);
		Y = _mm_setr_ps(0.f, 1.f, 0.f, 0.f);
		Z = _mm_setr_ps(0.f, 0.f, 1.f, 0.f);
		W = _mm_setr_ps(0.f, 0.f, 0.f, 1.f);
	}

	inline Vect4<float> operator *(const Vect4<float>& rhs) const
	{
		__m128 T = _mm_load_ps(&rhs.x);

		//Alternativement :
		//__m128 T = *((__m128 *)(&rhs.x));

		__m128 T0 = _mm_shuffle_ps(T, T, 0x00);
		__m128 T1 = _mm_shuffle_ps(T, T, 0x55);
		__m128 T2 = _mm_shuffle_ps(T, T, 0xAA);
		__m128 T3 = _mm_shuffle_ps(T, T, 0xFF);

		T0 = _mm_mul_ps(X, T0); //X * rhs.x
		T1 = _mm_mul_ps(X, T1); //Y * rhs.y
		T2 = _mm_mul_ps(X, T2);
		T3 = _mm_mul_ps(X, T3);

		T0 = _mm_add_ps(T0, T1);
		T2 = _mm_add_ps(T2, T3);
		T0 = _mm_add_ps(T0, T2);

		__declspec(align(16)) Vect4<float> result;
		_mm_store_ps(&result.x, T0);

		return result;
	}

	__m128 X;
	__m128 Y;
	__m128 Z;
	__m128 W;

	
};
*/
typedef Mat2<float> Matrix2;
typedef Mat3<float> Matrix3;
typedef Mat4<float> Matrix4;

// --- helpers
Matrix4 MultiplyMatrix(const Matrix4 &A, const Matrix4 &B);
Matrix4 MultiplyMatrix(const Matrix4 *A, const Matrix4 *B);

// rotations
Matrix4 RotateX(float angle);
Matrix4 RotateY(float angle);
Matrix4 RotateZ(float angle);

Matrix4 Translate(float x, float y, float z);

// camera ---
Matrix4 LookAt(const Vector3 &eye, const Vector3 &target, const Vector3 &up);

// projection ---
Matrix4 Ortho(float left, float right, float bottom, float top, float near, float far);
Matrix4 Frustum(float left, float right, float bottom, float top, float near, float far);
Matrix4 Perspective(float fovy, float aspect, float near, float far);


#endif // DEF_Mat